#include "gstreamer_6.h"

static void print_pad_templates_information(GstElementFactory *);

static void print_caps(const GstCaps *, const gchar *);

static gboolean print_field(GQuark, const GValue *, gpointer);

static void print_caps_capabilities(GstElement *, gchar *);

int play_05(int argc, char *argv[])
{
    GstElement *pipeline, *source, *sink;
    GstElementFactory *source_factory, *sink_factory;
    GstBus *bus;
    GstMessage *message;
    GstStateChangeReturn ret;
    gboolean terminal = false;

    gst_init(&argc, &argv);

    source_factory = gst_element_factory_find("audiotestsrc");
    sink_factory = gst_element_factory_find("autoaudiosink");

    if (!source_factory || !sink_factory)
    {
        g_printerr("Not all elements could be created.\n");
        return -1;
    }

    print_pad_templates_information(source_factory);
    print_pad_templates_information(sink_factory);

    source = gst_element_factory_create(source_factory, "source");
    sink = gst_element_factory_create(sink_factory, "sink");

    pipeline = gst_pipeline_new("test-pipeline");

    if (!pipeline || !source || !sink)
    {
        g_printerr("Not all element could be created.\n");
        return -1;
    }

    gst_bin_add_many(GST_BIN(pipeline), source, sink, nullptr);
    if (!gst_element_link_many(source, sink, nullptr))
    {
        g_printerr("Audio elements could not be linked!\n");
        g_object_unref(pipeline);
        return -1;
    }

    // g_object_set(source, "uri", "https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm", nullptr);

    ret = gst_element_set_state(pipeline, GST_STATE_PLAYING);
    if (ret == GST_STATE_CHANGE_FAILURE)
    {
        g_printerr("Unable to set the pipeline to playing state.\n");
        g_object_unref(pipeline);
        return -1;
    }

    bus = gst_element_get_bus(pipeline);
    do
    {
        message = gst_bus_timed_pop_filtered(bus, GST_CLOCK_TIME_NONE, (GstMessageType)(GST_MESSAGE_ERROR | GST_MESSAGE_EOS | GST_MESSAGE_STATE_CHANGED));

        if (message)
        {
            GError *err;
            char *debug_info;

            switch (GST_MESSAGE_TYPE(message))
            {
            case GST_MESSAGE_ERROR:
                gst_message_parse_error(message, &err, &debug_info);
                g_printerr("Error received from element %s:%s\n", GST_OBJECT_NAME(message->src), err->message);
                g_printerr("Debugging information: %s\n", debug_info ? debug_info : "none");
                g_clear_error(&err);
                g_free(debug_info);
                terminal = true;
                break;
            case GST_MESSAGE_EOS:
                g_print("End of stream has been reached!\n");
                terminal = true;
                break;
            case GST_MESSAGE_STATE_CHANGED:
                if (GST_MESSAGE_SRC(message) == GST_OBJECT(pipeline))
                {
                    GstState old_state, new_state, pending_state;
                    gst_message_parse_state_changed(message, &old_state, &new_state, &pending_state);
                    g_print("Pipeline state change from %s to %s.\n", gst_element_state_get_name(old_state), gst_element_state_get_name(new_state));

                    print_caps_capabilities(sink, "sink");
                }
                break;
            default:
                g_printerr("Unexcepted message received!\n");
                break;
            }
            gst_message_unref(message);
        }
    } while (!terminal);

    gst_object_unref(bus);
    gst_element_set_state(pipeline, GST_STATE_NULL);
    gst_object_unref(pipeline);
    gst_object_unref(source_factory);
    gst_object_unref(sink_factory);

    return 0;
}

static void print_pad_templates_information(GstElementFactory *factory)
{
    const GList *pads;

    GstStaticPadTemplate *pad_template;

    g_print("Pad templates for %s\n", gst_element_factory_get_longname(factory));
    if (!gst_element_factory_get_num_pad_templates(factory))
    {
        g_print("   none\n");
        return;
    }

    pads = gst_element_factory_get_static_pad_templates(factory);
    while (pads)
    {
        pad_template = static_cast<GstStaticPadTemplate *>(pads->data);
        pads = g_list_next(pads);

        if (pad_template->direction == GST_PAD_SRC)
        {
            g_print("   SRC template '%s'\n", pad_template->name_template);
        }
        else if (pad_template->direction == GST_PAD_SINK)
        {
            g_print("   SINK template '%s'\n", pad_template->name_template);
        }
        else
        {
            g_print("   UNKNOW !!! template '%s'\n", pad_template->name_template);
        }

        if (pad_template->presence == GST_PAD_ALWAYS)
        {
            g_print("   Availability: Always.\n");
        }
        else if (pad_template->presence == GST_PAD_SOMETIMES)
        {
            g_print("   Availability: Sometimes.\n");
        }
        else if (pad_template->presence == GST_PAD_REQUEST)
        {
            g_print("   Availability: Request.\n");
        }
        else
        {
            g_print("   Availability: UNKNOW!!!\n");
        }

        if (pad_template->static_caps.string)
        {
            GstCaps *caps;
            g_print("   Capabilities:\n");
            caps = gst_static_caps_get(&pad_template->static_caps);
            print_caps(caps, "  ");
            gst_caps_unref(caps);
        }

        g_print("\n");
    }
}

static void print_caps(const GstCaps *caps, const gchar *prefix)
{
    guint i;

    g_return_if_fail(caps != nullptr);

    if (gst_caps_is_any(caps))
    {
        g_printerr("%sANY\n", prefix);
        return;
    }

    if (gst_caps_is_empty(caps))
    {
        g_printerr("%sEMPTY\n", prefix);
        return;
    }

    for (i = 0; i < gst_caps_get_size(caps); i++)
    {
        GstStructure * structure = gst_caps_get_structure(caps, i);

        g_print("%s%s\n",prefix, gst_structure_get_name(structure));
        gst_structure_foreach(structure, print_field, (gpointer)prefix);
    }
}

static gboolean print_field(GQuark field, const GValue * value, gpointer prefix) {
    gchar * str = gst_value_serialize(value);

    g_print("%s %15s:%s\n", (gchar *)prefix, g_quark_to_string(field), str);
    g_free(str);
    return true;
}

static void print_caps_capabilities(GstElement * element, gchar * pad_name) {
    GstPad * pad = nullptr;
    GstCaps * caps = nullptr;

    pad = gst_element_get_static_pad(element, pad_name);
    if(!pad) {
        g_printerr("Could not retrieve pad '%s'\n", pad_name);
        return;
    }

    caps = gst_pad_get_current_caps(pad);
    if(!caps) {
        caps = gst_pad_query_caps(pad, nullptr);
    }

    g_print("Caps for the %s pad:\n", pad_name);
    print_caps(caps, "  ");
    gst_caps_unref(caps);
    g_object_unref(pad);
}
